package com.jy.api.util;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

@Component
public class RedisSyncLocker<R, P> {
    private static final String LOCKED_KEY_PREFIX = "_LOCK(%s)";
    private static final int TIMEOUT_MILLI_SECONDS = 30000;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 同步方法块
     *
     * @param lockedKey 加锁的KEY（自由定制）
     * @param executor  通知执行实现
     * @param o         输入参数
     * @return 同步返回参数
     * @throws Exception
     */
    public R keyLock(String lockedKey, ISyncExecutor<R, P> executor, P o) throws Exception {
        long startTime = System.currentTimeMillis();
        boolean canProcess = lock(lockedKey);
//        System.out.println("RedisSyncLocker: " + lockedKey + "," + canProcess);
        while (!canProcess) {
            if (System.currentTimeMillis() - startTime > TIMEOUT_MILLI_SECONDS) {
                del(lockedKey);
                throw new Exception("操作失败，错误：超时");
            }
            canProcess = lock(lockedKey);
//            System.out.println("RedisSyncLocker: " + lockedKey + "," + canProcess);
            Thread.sleep(10);
        }
        R result;

        try {
            result = executor.execute(o);
            del(lockedKey);
        } catch (Exception ex) {
            del(lockedKey);
            throw ex;
        }
        return result;
    }

    public R StringKeyLock(String id, ISyncExecutor<R, P> executor) throws Exception {
        return this.keyLock(String.format("KEY::%s", id), executor, null);
    }

    /**
     * 同步方法块
     *
     * @param id       加锁的KEY（自由定制）
     * @param executor 通知执行实现
     * @return 同步返回参数
     * @throws Exception
     */
    public R idLock(String id, ISyncExecutor<R, P> executor) throws Exception {
        return this.keyLock(String.format("ID(%s)", id), executor, null);
    }

    private boolean lock(String key) {
        String lock = String.format(LOCKED_KEY_PREFIX, key);
        final Boolean result = (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis();
            Boolean b = connection.setNX(lock.getBytes(), String.valueOf(expireAt).getBytes());
            connection.expire(lock.getBytes(), 1800);
            return b;
        });
//        System.out.println("RedisSyncLocker: lock " + lock + "," + result);
        return result;
    }

    private void del(String key) {
        String lock = String.format(LOCKED_KEY_PREFIX, key);
//        System.out.println("RedisSyncLocker: del " + lock);
        redisTemplate.execute((RedisCallback) connection -> {
            connection.del(lock.getBytes());
            return true;
        });
    }
}
